#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mpi.h>

#define TOTAL_CHARS 1000
#define TOTAL_WORDS 100
#define FILE_NAME "shakespe.txt"

int palindrome_word(char *); // verifica se palavra é palindromo
int palindrome_phrase(char [][25], int); // verifica se frase é palindromo
void clean_tokens(char [][25], int *); // limpa vetor de tokens
int bline(char **, char *); // Retorna 1 linha do bloco
int fsize(char *); // Retorna o tamanho do arquivo lido

double palindrometime = 0; // Tempo total gasto para calcular os palindromos

int main (int argc, char *argv[]) {

  /*
    totalw - total de palavras palindromes
    totalprime - total de palavras palindromes e primas
    bsize - tamanho de cada bloco (divisão do arquivo pelo número de processos) em bytes
    fname - nome do arquivo a ser lido
    nproc - número de processos utilizados
    rank - identificador de cada processo no programa
    tstart - hora de início de execução da rotina
    tend - hora de término de execução da rotina
  */
  int totalw = 0, totalp = 0, bsize;
  char fname[30];

  int nproc, rank;
  double tstart, tend;
  MPI_Status status;

  // Recebe nome do arquivo por argumento, ou pelo arquivo default "wikipedia.txt"
  strcpy(fname, (argc > 1 && argv[1] != NULL) ? argv[1] : FILE_NAME);

  // Recebe hora de início de execução da rotina
  tstart = MPI_Wtime();

  // Inicia execução da MPI
	if(MPI_Init(&argc, &argv) != MPI_SUCCESS)
		exit(1);

	MPI_Comm_size(MPI_COMM_WORLD, &nproc); // Recupera o número de processos que o programa está sendo dividido
	MPI_Comm_rank(MPI_COMM_WORLD, &rank); // Recupera o identificador do processo sendo rodado

  // Se for menos de 1 processo (impossível), o programa termina
  if(nproc <= 1)
    exit(0);

  // Calcula tamanho do bloco de texto para cada processo, dependendo do número de processos
  bsize = fsize(fname)/(nproc-1);

  // Se for o processo mestre
  if (rank == 0) {

    FILE *f;
    char *block = (char*) calloc(bsize+1, sizeof(char));
    int i, auxtotalw = 0, auxtotalp = 0;

    // Le o arquivo em blocos, e envia para os processos escravos processarem os palindromos
    for(i=0 ; i < nproc-1 ; i++) {
      if (!(f = fopen(fname, "r")) || fseek(f, bsize*i, SEEK_SET) ||
          !fread(block, sizeof(char), bsize, f))
        exit(1);

      // Envia bloco com texto extraído do arquivo para um dos processos escravos
      MPI_Send(block, bsize+1, MPI_CHAR, i+1, 1, MPI_COMM_WORLD);
    }

    fclose(f);

    // Espera resposta de todos os processos escravos com o número de palavras palindromes e primas que o bloco continha
    for(i=0 ; i < nproc-1 ; i++) {
      MPI_Recv(&auxtotalw, 1, MPI_INT, i+1, 1, MPI_COMM_WORLD, &status);
      MPI_Recv(&auxtotalp, 1, MPI_INT, i+1, 1, MPI_COMM_WORLD, &status);

      totalw += auxtotalw;
      totalp += auxtotalp;
    }

    // Finaliza o tempo de execução da rotina
    tend = MPI_Wtime();

    printf("Total de palavras palindromes: %d\n", totalw);
    printf("Total de frases palindromes: %d\n", totalp);
    printf("Tempo total: %f\n", tend-tstart);
    printf("Tempo de calculo dos palindromos: %f\n", palindrometime);

  } else { // Qualquer outro processo escravo

    char tokens[TOTAL_WORDS][25], line[TOTAL_CHARS];
    int ntokens = 0, i, auxtotalw = 0, auxtotalp = 0;
    char *block = (char*) calloc(bsize+1, sizeof(char));

    // Recebe bloco lido do arquivo no processo mestre
    MPI_Recv(block, bsize+1, MPI_CHAR, 0, 1, MPI_COMM_WORLD, &status);

    bline(&block, line); // Recupera a primeira linha do bloco

    do {

      char *token, *rest, *ptr = line;

      // Separa a linha do bloco em tokens (palavras) utilizando a função strtok_r
      while((token = strtok_r(ptr, " ,.-()", &rest))) {
        strcpy(tokens[ntokens], token);
        ntokens++;
        ptr = rest;
      }
      
      double taux = MPI_Wtime();

      // Percorre o array de tokens verificando se é palindromo
      for(i=0 ; i<ntokens ; i++)
        if(palindrome_word(tokens[i]))
          auxtotalw++;

      // Percorre o array de tokens verificando se a frase é palindrome
      if(palindrome_phrase(tokens, ntokens))
        auxtotalp++;

      palindrometime += (MPI_Wtime() - taux); // Soma o tempo de calculo do palindromo dessa iteração
  
      // Limpa o array tokens para a proxima iteração
      clean_tokens(tokens, &ntokens);

    } while(bline(&block, line)); // Percorre todas as linhas do bloco até acabar

    // Envia ao processo pai as variáveis auxiliares contendo o número
    // de palavras palindromes e primas do bloco lido por aquele processo escravo
		MPI_Send(&auxtotalw, 1, MPI_INT, 0, 1, MPI_COMM_WORLD);
	  MPI_Send(&auxtotalp, 1, MPI_INT, 0, 1, MPI_COMM_WORLD);

  }

  // Finaliza a execução da MPI
  MPI_Finalize();

  return 0;
}

int palindrome_word(char *str) {

  int size, i, j, palindrome = 1;

  size = strlen(str);

  if(size > 1)
    for(i=0, j=size-1; i < size ; i++, j--)
      palindrome = palindrome && (str[i] == str[j]);
  else
    palindrome = 0;

  return palindrome;

}

int palindrome_phrase(char tokens[][25], int n_tokens) {

  int i, j, palindrome = 1; 

  if (n_tokens > 1)
    for(i=0, j=n_tokens-1; i < n_tokens ; i++, j--)
      palindrome = palindrome && (strcmp(tokens[i], tokens[j]) == 0);
  else
    palindrome = 0;

  return palindrome;

}

void clean_tokens(char tokens[][25], int *n_tokens) {

  int i;

  for(i=0; i<40 ;i++)
    strcpy(tokens[i], "");

  *n_tokens = 0;

}

int bline(char **block, char *str) {

  char c, aux[TOTAL_CHARS] = "";
  int i = 0;

  c = **block, (*block)++; // Caminha com o primeiro caracter do bloco

  if(c != '\0') {
    while((int)c != 13 && c != '\n' && c != '\0') { // Verifica se é quebra de linha ou fim de linha (^M, \n, \0)
      if((int)c != 9)
        aux[i] = c, i++;
      else
        aux[i] = ' ', i++;
      c = **block, (*block)++; // Caminha para o próximo caracter do bloco
    }

    strcpy(str, aux);

    return 1;
  } else
    return 0;

}

int fsize(char *fname) {

  int size;
  FILE *f;

  // Caminha com o ponteiro até o final do arquivo, e verifica o número de bytes, que corresponde ao tamanho total
  if(!(f = fopen(fname, "r")) || fseek(f, 0, SEEK_END) || !(size = ftell(f)))
    exit(1);

  fclose(f);

  return size;
}
